"""
Copyright 2012 Wieser Dietmar, Margit Mutschlechner.

This file is part of Multimedia Social Networking Platform.

Multimedia Social Networking Platform is free software: 
you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Multimedia Social Networking Platform is distributed
in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Multimedia Social Networking Platform.  
If not, see <http://www.gnu.org/licenses/>.

"""

from msnp.model import MediaModel, SettingsModel
from msnp.logger import Logger
from msnp import persistence
from msnp.persistence.mysql.player_accessors import AmarokAccessor
from msnp.persistence import XMLController, PersistenceController
from msnp.service import utils
from msnp.model.MediaModel import Genre

class SynchChangesWriter():
    """Class for synchronizing media content into the media database.
    
    An instance of this class is created and the processChange method is called
    for every item that has to be synchronized. It either inserts the item into the
    media database or updates the corresponding entry.
    Stores the id of all changed entries.
    
    Parameters:
        __changedMediaIds: A list of integers containing the media ids of all media database entries that were changed.
        __currentMediaIds: A list of media ids changed in the current execution of the processChange method.
    """
    def __init__(self):
        """The init method."""
        # stores media ids of all medias that got changed by this writer
        self.__changedMediaIds = []
        # to buffer changed media ids in the current call of processChange, gets added to changedMediaIds at end of function
        self.__currentMediaIds = []
    
    def getChangedMediaIds(self):
        """Returns the parameter __changedMediaIds."""
        return self.__changedMediaIds
    
    def processChange(self, extAudio, table=None, player=MediaModel.Player.AMAROK().getName()):
        """Processes the given audio and adds the id of the affected media database entry to the list.
        
        Depending on the value of table it calls either the __processChangeWithoutTable
        or the __processChangeWithTable method. Then it adds the affected id to the __changedMediaIds.
        
        Args:
            extAudio: The Audio object that has to be processed.
            table: A String representing the table where the audio should be inserted.
            player: A String representing the player where the audio should be inserted. Default it is AMAROK.
        """
        self.__currentMediaIds = []
        if table == None:
            self.__processChangeWithoutTable(extAudio, player)
        else:
            self.__processChangeWithTable(extAudio, table, player)
        
        # add current changed media ids to all changed media ids
        if self.__currentMediaIds != None and len(self.__currentMediaIds) > 0:
            for mediaId in self.__currentMediaIds:
                if self.__changedMediaIds.__contains__(mediaId) == False:
                    self.__changedMediaIds.append(mediaId)
    
    def __processChangeWithTable(self, extAudio, table, player):
        """Processes the given audio depending on the table and player.
        
        Depending on the value of table it calls one of the
        __updateOrInsert(Rating|Collection|Artist|Genre|Lyrics|Audio|Composer) methods.
        
        Args:
            extAudio: The Audio object that has to be processed.
            table: A String representing the table where the audio should be inserted.
            player: A String representing the player where the audio should be inserted.
            
        Returns:
            The method returns None.
        """
        if player == MediaModel.Player.AMAROK().getName():
            if table == AmarokAccessor.TABLE_STATISTICS:
                self.__updateOrInsertRating(extAudio)
            elif table == AmarokAccessor.TABLE_ALBUMS:
                self.__updateCollection(extAudio)
            elif table == AmarokAccessor.TABLE_ARTISTS:
                self.__updateOrInsertArtist(extAudio)
            elif table == AmarokAccessor.TABLE_GENRES:
                self.__updateOrInsertGenre(extAudio)
            elif table == AmarokAccessor.TABLE_LYRICS:
                self.__updateOrInsertLyrics(extAudio)
            elif table == AmarokAccessor.TABLE_TRACKS:
                self.__updateOrInsertAudio(extAudio)
                #comment changes are triggered from the trackstable
                self.__updateOrInsertRating(extAudio)
            elif table == AmarokAccessor.TABLE_COMPOSERS:
                self.__updateOrInsertComposers(extAudio)
        return None
    
    def __processChangeWithoutTable(self, extAudio, player):
        """Processes the given audio depending on the player.
        
        The method inserts or updates the artist, then the collection, the genre,
        the lyrics and the rating.
        
        Args:
            extAudio: The Audio object that has to be processed.
            player: A String representing the player where the audio should be inserted.
            
        Returns:
            The method returns None.
        """
        if player == MediaModel.Player.AMAROK().getName():
            artistId = self.__updateOrInsertArtist(extAudio)
            #artist must be in the db
            if artistId >= 0:
                self.__updateCollection(extAudio)
                genreId = self.__updateOrInsertGenre(extAudio)
#                changedMediaIds = self.__updateOrInsertAudio(extAudio, artistId, genreId)
                self.__updateOrInsertAudio(extAudio, artistId, genreId)
                self.__updateOrInsertLyrics(extAudio)
                self.__updateOrInsertRating(extAudio)
                # needed for logging if the update came from the network
#                self.__currentMediaIds = changedMediaIds
            else:
                Logger.info("problem during insert/update of artist -> id < 0")
        else:
            Logger.info("unknown player in synchchangeswriter " + player)
        return None
    
    def __updateCollection(self, extAudio):
        """Updates the collection of the given audio.
        
        The method updates the collection of the audio if there is an entry in the media database
        that corresponds to the collection and artist of the given audio.
        Then, it stores in __changedMediaIds the id of one entry with that artist and collection.
        If no corresponding collection and artist could be found it does nothing
        (inserts are done during track comparison).
        
        Args:
            extAudio: The Audio object that has to be processed.
        """
        if extAudio.getMedia() != None and extAudio.getMedia().getCollection() != None:
            collectionName = extAudio.getMedia().getCollection().getName()
            if collectionName != None and len(collectionName) > 0:
                artistName = extAudio.getArtist().getName()
                collection = persistence.PersistenceController.selectCollectionForNameAndArtist(collectionName, artistName)
                if collection != None:
                    retValue = persistence.PersistenceController.updateCollection(collection, extAudio.getMedia().getCollection())
                    if retValue == MediaModel.ReturnType.UPDATED:
                        audios = persistence.PersistenceController.selectAudiosForArtistAndCollection(artistName, collectionName)
                        if audios != None:
                            # take any audio to synchronize changed collection
                            changedMediaId = audios[0].getMedia().getId()
                            if self.__changedMediaIds.__contains__(changedMediaId) == False and self.__currentMediaIds.__contains__(changedMediaId) == False:
                                self.__currentMediaIds.append(changedMediaId)
                #insert is done during track-comparison
                
    def __updateOrInsertArtist(self, extAudio):
        """Updates or inserts the artist of the given audio.
        
        The method updates the artist of the audio if there is an entry in the media database
        that corresponds to the collection and artist of the given audio.
        If no corresponding artist could be found it inserts the artist of the given file.
        Then, it stores in __changedMediaIds the id of one entry with that artist and collection.
        
        Args:
            extAudio: The Audio object that has to be processed.
        
        Returns:
            The method returns either the id of the changed artist or -1 if no entry was inserted or updated.
        """
        if extAudio.getArtist() != None:
            artistName = extAudio.getArtist().getName()
            if artistName != None and len(artistName) > 0:
                artist = persistence.PersistenceController.selectArtistForName(artistName)
                if artist != None:
                    retValue = persistence.PersistenceController.updateArtist(artist, extAudio.getArtist())
                    artistId = artist.getId()
                else:
                    retValue = MediaModel.ReturnType.UPDATED
                    artistId = persistence.PersistenceController.insertArtist(extAudio.getArtist())
                
                if retValue == MediaModel.ReturnType.UPDATED:
                    audios = persistence.PersistenceController.selectAudiosForArtist(artistName)
                    if audios != None:
                        # take any audio to synchronize changed artist
                        changedMediaId = audios[0].getMedia().getId()
                        if self.__changedMediaIds.__contains__(changedMediaId) == False and self.__currentMediaIds.__contains__(changedMediaId) == False:
                            self.__currentMediaIds.append(changedMediaId)
                return artistId
        return - 1
                
    def __updateOrInsertGenre(self, extAudio): 
        """Updates or inserts the genre of the given audio.
        
        The method inserts the genre of the audio if there is a corresponding predefined genre.
        If no corresponding genre could be found no genre is inserted.
        If the genre already exists it does nothing.
        The audio is linked to the genre later on in another method.
        
        Args:
            extAudio: The Audio object that has to be processed.
        
        Returns:
            The method returns either the id of the inserted genre or -1 if no genre was inserted.
        """
        genreId = -1
        if extAudio.getMedia() != None and extAudio.getMedia().getGenre() != None:
            genreName = extAudio.getMedia().getGenre().getName()
            if genreName != None and len(genreName) > 0:
                genre = persistence.PersistenceController.selectGenreForName(genreName)
                if genre != None:
#                    retValue = persistence.PersistenceController.updateGenre(genre, extAudio.getMedia().getGenre())
                    genreId = genre.getId()
                else:
                    predefinedGenres = PersistenceController.selectPredefinedGenres()
                    newGenre = None
                    for predefinedGenre in predefinedGenres:
                        if utils.StringCompare.isEquals(predefinedGenre.getName(), genreName):
                            newGenre = Genre.createGenre(predefinedGenre.getName())
                            break
                    if newGenre != None:
                        genreId = persistence.PersistenceController.insertGenre(newGenre)
                        audios = persistence.PersistenceController.selectAudiosForGenre(predefinedGenre.getName())
                        if audios != None:
                            # take any audio to synchronize changed genre
                            changedMediaId = audios[0].getMedia().getId()
                            if self.__changedMediaIds.__contains__(changedMediaId) == False and self.__currentMediaIds.__contains__(changedMediaId) == False:
                                self.__currentMediaIds.append(changedMediaId)
        return genreId
            
    def __updateOrInsertLyrics(self, extAudio):
        """Updates or inserts the lyrics of the given audio.
        
        The method searches for the corresponding audio with artist, collection and title.
        If such an entry could be found it updates the lyrics.
        
        Args:
            extAudio: The Audio object that has to be processed.
        """
        if extAudio.getArtist() != None and extAudio.getMedia() != None and extAudio.getMedia().getCollection() != None:
            title = extAudio.getMedia().getTitle()
            artistName = extAudio.getArtist().getName()
            collectionName = extAudio.getMedia().getCollection().getName()
            audios = persistence.PersistenceController.selectAudiosForTitleArtistAndCollection(title, artistName, collectionName)
            if audios != None and len(audios) > 0:
                for audio in audios:
                    retValue = persistence.PersistenceController.updateAudio(audio, extAudio)
                    if retValue == MediaModel.ReturnType.UPDATED:
                        changedMediaId = audio.getMedia().getId()
                        if self.__changedMediaIds.__contains__(changedMediaId) == False and self.__currentMediaIds.__contains__(changedMediaId) == False:
                            self.__currentMediaIds.append(changedMediaId)
                            
    def __updateOrInsertComposers(self, extAudio):
        """Updates or inserts the composer of the given audio.
        
        The method searches for the corresponding audio with artist, collection and title.
        If such an entry could be found it updates the composer.
        
        Args:
            extAudio: The Audio object that has to be processed.
        """
        if extAudio.getArtist() != None and extAudio.getMedia() != None and extAudio.getMedia().getCollection() != None:
            title = extAudio.getMedia().getTitle()
            artistName = extAudio.getArtist().getName()
            collectionName = extAudio.getMedia().getCollection().getName()
            audios = persistence.PersistenceController.selectAudiosForTitleArtistAndCollection(title, artistName, collectionName)
            if audios != None and len(audios) > 0:
                for audio in audios:
                    retValue = persistence.PersistenceController.updateAudio(audio, extAudio)
                    if retValue == MediaModel.ReturnType.UPDATED:
                        changedMediaId = audio.getMedia().getId()
                        if self.__changedMediaIds.__contains__(changedMediaId) == False and self.__currentMediaIds.__contains__(changedMediaId) == False:
                            self.__currentMediaIds.append(changedMediaId)
                
    def __updateOrInsertRating(self, extAudio):
        """Updates or inserts the rating of the given audio.
        
        The method searches for the corresponding audio with artist, collection and title.
        If such an entry could be found it updates or inserts the rating.
        
        Args:
            extAudio: The Audio object that has to be processed.
        """
        if extAudio.getArtist() != None and extAudio.getMedia() != None and extAudio.getMedia().getCollection() != None:
            title = extAudio.getMedia().getTitle()
            artistName = extAudio.getArtist().getName()
            collectionName = extAudio.getMedia().getCollection().getName()
            audios = persistence.PersistenceController.selectAudiosForTitleArtistAndCollection(title, artistName, collectionName)
            extRatings = extAudio.getMedia().getRatings()
            if extRatings != None and len(extRatings) > 0 and audios != None and len(audios) > 0:
                for extRating in extRatings:
                    username = extRating.getUsername()
                    if extRating != None:
                        for audio in audios:
                            rating = audio.getRatingForUsername(username)
                            if rating != None:
                                retValue = persistence.PersistenceController.updateRating(rating, extRating)
                            else:
                                retValue = MediaModel.ReturnType.UPDATED
                                mediaId = audio.getMedia().getId()
                                persistence.PersistenceController.insertRating(extRating, mediaId)
                            if retValue == MediaModel.ReturnType.UPDATED:
                                changedMediaId = audio.getMedia().getId()
                                if self.__changedMediaIds.__contains__(changedMediaId) == False and self.__currentMediaIds.__contains__(changedMediaId) == False:
                                    self.__currentMediaIds.append(changedMediaId)
            else:
                Logger.info("no ratings in extaudio -> synchchange, __updateOrInsertRating")
                
    def __updateOrInsertAudio(self, extAudio, artistId=None, genreId=None):
        """Updates or inserts the given audio.
        
        The method searches for the corresponding audio with artist, collection and title.
        If such an entry could be found it updates or inserts the media and audio.
        
        Args:
            extAudio: The Audio object that has to be processed.
            artistId: The id of the audio's artist. Default None.
            genreId: The id of the audio's genre. Default None.
        """
        
        title = extAudio.getMedia().getTitle()
        artistName = extAudio.getArtist().getName()
        collectionName = extAudio.getMedia().getCollection().getName()
        audios = persistence.PersistenceController.selectAudiosForTitleArtistAndCollection(title, artistName, collectionName)
        genreName = extAudio.getMedia().getGenre().getName()
        #create genre and artist
        
        genre = persistence.PersistenceController.selectGenreForName(genreName)
            
        artist = None
        if artistId == None:
            artist = persistence.PersistenceController.selectArtistForName(artistName)
        if artistId >= 0:
            artist = MediaModel.Artist.createArtist(artistName, artistId)
            
        #get the collection -> insert it if not found
        isCollectionInserted = False
        collection = None
        if collectionName != None and len(collectionName) > 0:
            collection = persistence.PersistenceController.selectCollectionForNameAndArtist(collectionName, artistName)
            if collection == None:
                collectionId = persistence.PersistenceController.insertCollection(extAudio.getMedia().getCollection())
                if collectionId > 0:
                    isCollectionInserted = True
                    collection = MediaModel.Collection.createCollection(collectionName, collectionId)
                    
        extMedia = extAudio.getMedia()
        #set the found genre, collection and artist
        extMedia.setGenre(genre)
        extMedia.setCollection(collection)
        extAudio.setArtist(artist)
        
        #update
        if audios != None and len(audios) > 0:
#            changedMediaIdsToReturn = []
            for audio in audios:
                retValueMedia = persistence.PersistenceController.updateMedia(audio.getMedia(), extAudio.getMedia())
                retValueAudio = persistence.PersistenceController.updateAudio(audio, extAudio)
                if retValueAudio == MediaModel.ReturnType.UPDATED or retValueMedia == MediaModel.ReturnType.UPDATED:
                    changedMediaId = audio.getMedia().getId()
                    if self.__changedMediaIds.__contains__(changedMediaId) == False and self.__currentMediaIds.__contains__(changedMediaId) == False:
                        self.__currentMediaIds.append(changedMediaId)
        #insert
        else:
            insertOk = False
            if extMedia.getTitle() != None and len(extMedia.getTitle()) > 0:
                mediaId = persistence.PersistenceController.insertMedia(extMedia)
                if mediaId > -1:
                    extMedia.setId(mediaId)
                    #set the inserted media
                    extAudio.setMedia(extMedia)
                    audioId = persistence.PersistenceController.insertAudio(extAudio)
                    if audioId > -1:
                        insertOk = True
                        Logger.info("new audio inserted: " + str(audioId))
                    else:
                        Logger.info("unable to insert audio")
                        if persistence.PersistenceController.deleteMedia(mediaId):
                            Logger.info(" -> media deleted")
                else:
                    Logger.info("unable to insert media, mediaId is < -1: " + extMedia.getTitle())
            else:
                Logger.info("unable to insert media, title is None")
            #check if collection must be deleted
            if insertOk == False:
                if isCollectionInserted:
                    if persistence.PersistenceController.deleteCollection(collectionId):
                        Logger.info(" -> collection deleted")
            else:
                changedMediaId = extAudio.getMedia().getId()
                if self.__changedMediaIds.__contains__(changedMediaId) == False and self.__currentMediaIds.__contains__(changedMediaId) == False:
                    self.__currentMediaIds.append(changedMediaId)
                    
class SynchChangesReader():
    """Class for synchronizing media content from the media database towards the media player's database.
    
    Contains a single method to retrieve all entries of the media database changed since the last
    synchronization towards the media player.
    """
    
    def __init__(self):
        """The init method."""
        pass
    
    def getAudiosToSynch(self):
        """Retrieves all entries of the media table changed since the last synchronization towards the media player.
        
        Returns:
            This method returns a list of Audio objects that were changed since the last synchronization.
        """
        settings = XMLController.readAllSettings()
        lastProcessedId = settings.getCurrentUserSettings().getLastPlayerSynch().getLastProcessedId()
        changes = PersistenceController.selectNetSynchMediasFromNetworkProcessedSince(lastProcessedId)
        
        # store new last synch timestamp and id
        date = utils.DateFormatter.formatTimeWithDay()
        for change in changes:
            if change.getId() > lastProcessedId:
                lastProcessedId = change.getId()
        newLastSynch = SettingsModel.LastPlayerSynch(lastProcessedId, date)
        settings.getCurrentUserSettings().setLastPlayerSynch(newLastSynch)
        XMLController.storeSettings(settings)
        
        mediaIds = []
        for change in changes:
            mediaIds.append(change.getMediaId())
        
        audiosToSynch = PersistenceController.selectAudiosForMediaIds(mediaIds)
        
        return audiosToSynch
    
